package com.faithbj.process.impl;

import com.faithbj.process.*;
import com.google.common.base.Function;
import com.google.common.collect.*;
import org.jbpm.task.query.TaskSummary;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.*;

/**
 * User: xiongyuanwei
 * Date: 12-7-14
 * Time: 下午9:13
 */
@SuppressWarnings("unchecked")
public class ContentSearcher<T> {

    private Condition<T> condition;
    private String userId;
    private String taskName;
    private int first;
    private int limit;
    private TaskContentProvider taskContentProvider;
    private EntityManager em;
    private Function<Map<String, Object>, T> extractFn;
    private TaskContentFilter filter;
    private List<Long> satisfiedTaskIds = Collections.emptyList();
    private Map<Long, Map<String, Object>> contentMap = Collections.emptyMap();
    private Multimap<Object, Long> groupMap = HashMultimap.create();
    private List<String> groups = Arrays.asList("outer", "inner");

    ContentSearcher(Condition<T> condition, String userId, String taskName, int first, int limit,
            TaskContentProvider taskContentProvider, EntityManager em, Function<Map<String, Object>, T> extractFn,
            TaskContentFilter filter) {
        this.condition = condition;
        this.userId = userId;
        this.taskName = taskName;
        this.first = first;
        this.limit = limit;
        this.taskContentProvider = taskContentProvider;
        this.em = em;
        this.extractFn = extractFn;
        this.filter = filter;
    }

    public ContentSearcher<T> search() {
        int offset = 0;
        List<Long> taskIds = Lists.newLinkedList();
        contentMap = Maps.newHashMap();
        groupMap = LinkedListMultimap.create();
        if (limit == 0) return this;
        List<TaskIdContentIdTuple> tuples = getPotentialTaskIds();
        if (tuples.size() < first) return this;
        for (TaskIdContentIdTuple t: tuples) {
            Map<String, Object> content = taskContentProvider.getContentById(t.getContentId());
            if (filter.accepts(content) && isContentSatisfied(content)) {
                if (offset >= first) {
                    long taskId = t.getTaskId();
                    taskIds.add(taskId);
                    contentMap.put(taskId, content);
                    if (condition instanceof GroupableCondition) {
                        GroupableCondition groupableCondition = (GroupableCondition) condition;
                        if (isGroupKeySatisfied(content)) {
                            groupMap.put(groupableCondition.getGroupKey(extractFn.apply(content)), taskId);
                        }
                    }
                    if (taskIds.size() >= limit) {
                        break;
                    }
                }
                offset++;
            }
        }
        satisfiedTaskIds = taskIds;
        return this;
    }

    public List<TaskModel> getResults() {
        return fetchTaskModels(satisfiedTaskIds);
    }

    public int countSearch() {
        int count = 0;
        List<TaskIdContentIdTuple> tuples = getPotentialTaskIds();
        for (TaskIdContentIdTuple t: tuples) {
            Map<String, Object> content = taskContentProvider.getContentById(t.getContentId());
            if (filter.accepts(content) && isContentSatisfied(content)) {
                count++;
            }
        }
        return count;
    }

    public Multimap<Object, TaskModel> getGroupedResults() {
        Multimap<Object, TaskModel> ret = LinkedListMultimap.create();
        for (Object key : groupMap.keySet()) {
            Collection<Long> taskIds = groupMap.get(key);
            ret.putAll(key, fetchTaskModels(taskIds));
        }
        return ret;
    }

    private List<TaskIdContentIdTuple> getPotentialTaskIds() {
        boolean isAdmin = userId.equals("Administrator");
        String namedQuery = isAdmin ? "TaskIdsForContentQueryAsAdmin" : "TaskIdsForContentQuery";
        Query query = em.createNamedQuery(namedQuery);
        query.setParameter("taskName", taskName);
        if (!isAdmin) {
            query.setParameter("userId", userId);
            query.setParameter("groups", groups);
        }
        return query.getResultList();
    }

    @SuppressWarnings("unchecked")
    private List<TaskModel> fetchTaskModels(Collection<Long> taskIds) {
        if (taskIds == null || taskIds.size() == 0) return Collections.emptyList();
        Query query = em.createNamedQuery("TasksByIds");
        query.setParameter("taskIds", taskIds);
        List<TaskSummary> taskSummaries = query.getResultList();
        List<TaskModel> tasks = Lists.transform(taskSummaries, new Function<TaskSummary, TaskModel>() {
            @Override
            public TaskModel apply(TaskSummary taskSummary) {
                Map<String, Object> content = contentMap.get(taskSummary.getId());
                return new TaskModel(taskSummary.getId(), taskSummary.getName(),
                        taskSummary.getActualOwner() != null ? taskSummary.getActualOwner().getId() : null,
                        taskSummary.getCreatedOn(), taskSummary.getProcessInstanceId(), content);
            }
        });
        return Lists.newArrayList(tasks);
    }

    private boolean isContentSatisfied(Map<String, Object> content) {
        if (condition == null || condition.isEmpty()) return true;
        if (content == null) return false;
        else {
            T searchable = extractFn.apply(content);
            return searchable != null && condition.isSatisfiedBy(searchable);
        }
    }

    private boolean isGroupKeySatisfied(Map<String, Object> content) {
        if (condition == null || condition.isEmpty() || content == null) return false;
        else {
            T searchable = extractFn.apply(content);
            GroupableCondition groupableCondition = (GroupableCondition) condition;
            return searchable != null && groupableCondition.isGroupKeySatisfied(searchable);
        }
    }
}
